/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: service_proxy.cpp
 *
 * Purpose: service proxy
 *
 * Developer:
 *   wen.gu , 2021-10-11
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "icpp/com/service_proxy.h"

#include "icpp/com/service_finder_factory.h"
#include "icpp/com/response_builder.h"
#include "icpp/com/message_broker_simple.h"

#define LOG_TAG "srvp"
#include "icpp/core/log.h"
namespace icpp
{
namespace com
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
class ServiceProxy::Impl
{
public:
    uint8_t interface_version_ = 0;
    MessageBrokerPtr msg_broker_ = nullptr;
    std::mutex lock_;
    ServiceFinder::ServiceFinderPtr service_finder_ = nullptr;
    ServiceInfo service_info_;    
public:
    IcppErrc doConnect(const ServiceInfo& srv_info);
    IcppErrc disconnect();
public:
    void resubscribe();  /** resubscribe event */  
protected:
    IcppErrc doInitialize(const ServiceInfo& srv_info);    
};
/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
ServiceProxy::IcppErrc ServiceProxy::Impl::disconnect()
{
    IcppErrc ret = msg_broker_->stop();

    if (IcppErrc::OK != ret)
    {
        return ret;
    }

    return msg_broker_->uninitialize();
}

core::IcppErrc ServiceProxy::Impl::doInitialize(const ServiceInfo& srv_info)
{
    MessageBrokerInitializeParam param;
    param.broker_name = "cli: " + srv_info.service_name;
    param.protocol_version = COM_PROTOCOL_VERSION;
    param.interface_version = srv_info.interface_version;
    param.connection_state_handler = [this](EndpointId id, bool is_connected)
    {
        if (is_connected) /** todo refine me?? */
        {
            //this->msg_broker_->set_service_id(id);
            this->resubscribe();
        }
        else
        {
            Result<void> ret = service_finder_->startFindServiceAsync([this](const Result<ServiceInfo>& res_info)
            {
                if (res_info.hasValue() == false)
                {
                    return ;
                }
                ServiceInfo sinfo  = res_info.value();
                IcppErrc errc = IcppErrc::Undefined;
                if (this->service_info_ ==  sinfo)
                {/** the adress not changed, so just stop and start */
                    this->msg_broker_->stop();
                    errc = this->msg_broker_->start();
                }
                else
                {/** the service info changed, then uninitialize and re inilitialize */
                    this->msg_broker_->uninitialize();
                    errc = this->doConnect(sinfo);
                }

                if (IcppErrc::OK == errc)
                {
                    ServiceFinder::ServiceFinderPtr finder_ptr = this->service_finder_;
                    std::thread thd([finder_ptr](){
                    finder_ptr->stopFindServiceAsync();
                    });
                    thd.detach();                        
                }
            });
        }

    };  

    IcppErrc ret = msg_broker_->initialize(srv_info.urls, param);

    if (IcppErrc::OK != ret)
    {
        LOGE("initialize message broker as 'client' failed(%s)\n", core::ErrorStr(ret));        
    }

    return ret;
}

core::IcppErrc ServiceProxy::Impl::doConnect(const ServiceInfo& srv_info)
{
    if (interface_version_ != srv_info.interface_version)
    {
        LOGE("interface between serivce(%d) and client(%d) is missmatch\n", srv_info.interface_version, interface_version_);
        return IcppErrc::InterfaceVersionErr;
    }

    IcppErrc ret = doInitialize(srv_info);
    if (IcppErrc::OK != ret)
    {
        return ret;
    }

    ret = msg_broker_->start();

    if (IcppErrc::OK != ret)
    {
        msg_broker_->uninitialize();
        return ret;
    }

    service_info_ = srv_info;
    return IcppErrc::OK;    
}

void ServiceProxy::Impl::resubscribe()  /** resubscribe event */
{
    msg_broker_->resubscribe(MessageType::kNotification, MessageType::kSubscribe);
    msg_broker_->resubscribe(MessageType::kPropertyNotify, MessageType::kPropertySubcribe);
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
ServiceProxy::ServiceProxy(ServiceFinder::ServiceFinderPtr service_finder, uint8_t interface_version /*= 0*/)
    :impl_(new Impl)
{
    impl_->service_finder_ = service_finder;
    impl_->interface_version_ = interface_version;
    impl_->msg_broker_ = std::make_shared<MessageBrokerSimple>(MessageBrokerRole::kClient);
    /** todo something */
}

ServiceProxy::~ServiceProxy()
{
    /** todo something */
}

/**
 * start to connect to service and wait unitil timeout or successfull connect to service
 */
core::IcppErrc ServiceProxy::connect(int32_t timeout_ms/* = CONNECT_WAIT_INFINITE*/)
{
    core::AutoLock al(impl_->lock_);
    if (impl_->service_finder_ == nullptr)
    {
        return IcppErrc::InvalidStatus;
    }

    Result<ServiceInfo> ret = impl_->service_finder_->findService(timeout_ms);

    if (ret.hasValue())
    {
        return impl_->doConnect(ret.value());
    }

    return ret.error();
}

/**
 * try to connect to service, and return result immediately.
*/
core::IcppErrc ServiceProxy::tryConnect()
{
    core::AutoLock al(impl_->lock_);
    if (impl_->service_finder_ == nullptr)
    {
        return IcppErrc::InvalidStatus;
    }

    Result<ServiceInfo> ret = impl_->service_finder_->tryFindService();

    if (ret.hasValue())
    {
        return impl_->doConnect(ret.value());
    }

    return ret.error();
}

/**
 * cancel connect  when Connect method called and in waiting state. 
 */
void ServiceProxy::cancelConnect()
{
    core::AutoLock al(impl_->lock_);
    if (impl_->service_finder_ == nullptr)
    {
        return ;
    }

    Result<void> ret = impl_->service_finder_->cancelFindService();
}

/** disconnect from service*/
core::IcppErrc ServiceProxy::disconnect()
{
    return impl_->disconnect();
}

/**
 * start to connect to service, the result will return by callback  'handler'
 */
core::IcppErrc ServiceProxy::startConnectAsync(ConnectHandler handler)
{
    if (handler == nullptr)
    {
        return IcppErrc::BadParameter;
    }

    core::AutoLock al(impl_->lock_);
    if (impl_->service_finder_ == nullptr)
    {
        return IcppErrc::InvalidStatus;
    }

    Result<void> ret = impl_->service_finder_->startFindServiceAsync([this, handler](const Result<ServiceInfo>& res_info)
    {
        if (!res_info.hasValue())
        {
            return ;
        }

        if (this->impl_->doConnect(res_info.value()) == IcppErrc::OK)
        {
            handler(true);
        }

        ServiceFinder::ServiceFinderPtr finder_ptr = this->impl_->service_finder_;
        std::thread thd([finder_ptr](){
            finder_ptr->stopFindServiceAsync();
        });
        thd.detach();
    });

    if (ret.hasValue())
    {
        return IcppErrc::OK;
    }

    return ret.error();
}

core::IcppErrc ServiceProxy::stopConnectAsync()
{
    core::AutoLock al(impl_->lock_);
    if (impl_->service_finder_ == nullptr)
    {
        return IcppErrc::InvalidStatus;
    }

    Result<void> ret = impl_->service_finder_->stopFindServiceAsync();

    return ret.hasValue() ? IcppErrc::OK : ret.error();
}

MessageBroker::MessageBrokerPtr ServiceProxy::broker()
{
    return impl_->msg_broker_;
}


} /** namespace com */
} /** namespace icpp */